﻿using System;
#if WINDOWS_PHONE
using StyleMVVM.Data.Collections;
#else
using System.Collections.Concurrent;
#endif
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.DependencyInjection;
using StyleMVVM.LanguageExtensions;
using StyleMVVM.Utilities;
#if !DOT_NET
using Windows.Foundation;
using Windows.Storage;
#endif
#if NETFX_CORE
using Windows.UI.Xaml;

#else
using System.Windows;

#endif

namespace StyleMVVM.Logging
{
	public sealed class FileLogAppender : ILogAppender
	{
		private int maxProcessAtOneTime = 1000;
		private volatile bool holdQueue = false;
		private int queueDepth = 10;
		private readonly object outputLock = new object();
		private readonly ConcurrentQueue<LogEntry> pendingMessages = new ConcurrentQueue<LogEntry>();
		private string logFile;

#if DOT_NET

#else
		private StorageFolder logFolder;
#endif

		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(FileLogAppender)).As(typeof(ILogAppender));
		}

#if NETFX_CORE
		public IAsyncOperation<bool> Configure()
		{
			return internalConfigure().AsAsyncOperation();
		}
#else
		public Task<bool> Configure()
		{
			return internalConfigure();
		}
#endif

		public async void Flush()
		{
			try
			{
				await RequestProcessQueue();
			}
			catch (Exception)
			{
			}
		}

		private async Task<bool> internalConfigure()
		{
#if DOT_NET

#else
			logFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("Logs", CreationCollisionOption.OpenIfExists);
#endif

			folderCleanup();

			return true;
		}

		public LogLevel LoggingLevel { get; set; }

		public int QueueDepth
		{
			get { return queueDepth; }
			set { queueDepth = value; }
		}

		public void Log(LogEntry entry)
		{
			if (entry.Level >= LoggingLevel)
			{
				pendingMessages.Enqueue(entry);

				if (pendingMessages.Count > QueueDepth && !holdQueue)
				{
					RequestProcessQueue();
				}
			}
		}

		private async Task RequestProcessQueue()
		{
			bool outputThread = false;

			lock (outputLock)
			{
				if (!holdQueue)
				{
					holdQueue = true;
					outputThread = true;
				}
			}

			if (outputThread)
			{
				LogEntry output;
				List<LogEntry> entries = new List<LogEntry>();

				for (int i = 0; i < maxProcessAtOneTime; i++)
				{
					if (pendingMessages.TryDequeue(out output))
					{
						entries.Add(output);
					}
					else
					{
						break;
					}
				}

				await ProcessQueue(entries);

				holdQueue = false;

				if (pendingMessages.Count > queueDepth)
				{
					await RequestProcessQueue();
				}
			}
		}

		private async void folderCleanup()
		{
			if (!DesignMode.IsEnabled)
			{
#if DOT_NET

#else
				try
				{
					string logFileStart = "Application.";

					foreach (var file in await logFolder.GetFilesAsync())
					{
						if (DateTime.Now.Subtract(file.DateCreated.Date).TotalDays > 10)
						{
							try
							{
								await logFolder.DeleteAsync();
							}
							catch (Exception)
							{
							}
						}
					}
				}
				catch (Exception)
				{
				}
#endif
			}
		}

		private string GetLogFileName()
		{
			if (string.IsNullOrEmpty(logFile))
			{
				DateTime now = DateTime.Now;
				logFile = string.Format("Application.{0}-{1}-{2}.log", now.Month, now.Day, now.Year);
			}

			return logFile;
		}

		private async Task ProcessQueue(IEnumerable<LogEntry> entries)
		{
			try
			{
#if DOT_NET
				List<string> outputStrings = new List<string>();

				foreach (LogEntry logEntry in entries)
				{
					outputStrings.Add(FormatLogEntryString(logEntry));

					if (logEntry.Exception != null)
					{
						outputStrings.Add(FormatExcetpionString(logEntry.Exception));
					}
				}

				File.AppendAllLines(GetLogFileName(), outputStrings);
#else
				StorageFile file =
					await logFolder.CreateFileAsync(GetLogFileName(),
					                                CreationCollisionOption.OpenIfExists);

				using (Stream fileStream = await file.OpenStreamForWriteAsync())
				{
					fileStream.Seek(0, SeekOrigin.End);

					foreach (LogEntry logEntry in entries)
					{
						fileStream.WriteLine(FormatLogEntryString(logEntry));

						if (logEntry.Exception != null)
						{
							fileStream.WriteLine(FormatExcetpionString(logEntry.Exception));
						}
					}
				}
#endif
			}
			catch (Exception)
			{
				// this is one of those riddles ... who should the logger log to?
			}
		}

		private string FormatLogEntryString(LogEntry logEntry)
		{
			return string.Format("{0:MM/dd/yyyy HH:mm:ss:fff} - {1} {2}: {3}",
			                     logEntry.EventTime.DateTime,
			                     logEntry.Level,
			                     logEntry.Supplemental,
			                     logEntry.Message);
		}

		private string FormatExcetpionString(Exception exp)
		{
			StringBuilder exceptionString = new StringBuilder("Exception: ");

			exceptionString.AppendLine(exp.Message);

			exceptionString.AppendLine(exp.StackTrace);

			return exceptionString.ToString();
		}
	}
}